package com.futureCloudMOM.common.utils.core;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.futureCloudMOM.common.annotation.Alias;
import com.futureCloudMOM.common.annotation.Condition;
import com.futureCloudMOM.common.core.domain.dto.Search;
import com.futureCloudMOM.common.core.domain.dto.Sorter;
import com.futureCloudMOM.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 文件名：QueryWrapperUtils
 *
 * @author lei.wang95@rivo-robot.com
 * @since 2022/4/20 10:35
 */
@Slf4j
public class QueryWrapperUtils {

    private static final String SEARCH_PARAM_NAME = "searchParam";
    private static final String SORTER = "sorter";

    /**
     * 根据请求对象生成 QueryWrapper
     *
     * @param request 请求对象
     * @param <T>     查询实体
     * @return QueryWrapper
     */
    public static <T> QueryWrapper<T> create(HttpServletRequest request) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        List<Search> conditions;
        if (containsSearch(request) && !CollectionUtils.isEmpty(conditions = JSON.parseArray(request.getParameter(SEARCH_PARAM_NAME), Search.class))) {
            wrapper = create(conditions);
        }
        return wrapper;
    }

    /**
     * 将条件列表转换为QueryWrapper
     *
     * @param searches 查询条件列表
     * @param <T>      实体
     * @return QueryWrapper
     */
    private static <T> QueryWrapper<T> create(List<Search> searches) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        for (Search search : searches) {
            Object value = search.getValue();
            if (StringUtils.hasText(search.getKeyword()) && StringUtils.hasText(search.getColumn())) {
                try {
                    if (ObjectUtils.isEmpty(value)) {
                        continue;
                    }
                    set(queryWrapper, search, value);
                } catch (InvocationTargetException | IllegalAccessException e) {
                    log.error("error={}", e.getCause().toString());
                }
            }
        }
        return queryWrapper;
    }

    /**
     * 将条件列表转换为QueryWrapper
     *
     * @param wrapper 查询 wrapper
     * @param request 请求对象
     */
    public static void addSorter(QueryWrapper<?> wrapper, HttpServletRequest request) {
        List<Sorter> sorters;
        if (containsSorter(request) && !CollectionUtils.isEmpty(sorters = JSON.parseArray(request.getParameter(SORTER), Sorter.class))) {
            if (!CollectionUtils.isEmpty(sorters)) {
                sorters.forEach(sorter -> {
                    String column;
                    wrapper.orderBy(StringUtils.hasText(column = sorter.getColumn()), "asc".equals(sorter.getOrderBy()), column);
                });
            }
        }
    }

    /**
     * 根据查询实例构建 QueryWrapper
     *
     * @param searchObj 查询对象
     * @param useAlias  是否使用别名
     * @param <T>       泛型
     * @return new QueryWrapper
     */
    public static <T> QueryWrapper<T> create(Object searchObj, boolean useAlias) {
        String primaryAlias = null, selfAlias;
        boolean noAlias = false;
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntityClass(queryWrapper.getEntityClass());
        if (useAlias) {
            // 若开启别名，先判断当前类有没有标记别名
            Class<?> aClass = searchObj.getClass();
            if (!aClass.isAnnotationPresent(Alias.class) || !StringUtils.hasText(primaryAlias = aClass.getAnnotation(Alias.class).value())) {
                noAlias = true;
                log.warn("class {} no alias or empty string!", aClass.getName());
            }
        }
        for (Field field : getAllFields(searchObj.getClass())) {
            field.setAccessible(true);
            try {
                if (!field.isAnnotationPresent(Condition.class)) {
                    continue;
                }
                Condition condition = field.getAnnotation(Condition.class);
                Object value = field.get(searchObj);
                if (Objects.isNull(value)) {
                    continue;
                }
                Search search = parse(condition);
                if (ObjectUtils.isEmpty(search.getColumn())) {
                    search.setColumn(field.getName());
                }
                if (ObjectUtils.isEmpty(search.getKeyword())) {
                    search.setKeyword("eq");
                }
                if (useAlias) {
                    selfAlias = condition.alias();
                    if (StringUtils.hasText(selfAlias)) {
                        // 字段获取自身取的别名
                        search.setColumn(selfAlias + "." + search.getColumn());
                        set(queryWrapper, search, value);
                        continue;
                    }
                    log.warn("field {} no alias or empty string!", field.getName());
                    if (noAlias) {
                        // 没有别名，忽略
                        continue;
                    }
                    // 字段获取当前类取的别名
                    search.setColumn(primaryAlias + "." + search.getColumn());
                }
                log.debug("==> set field [{}]", field.getName());
                set(queryWrapper, search, value);
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.error("error={}", e.getCause().toString());
            }
        }
        return queryWrapper;
    }

    /**
     * 获取所有字段
     *
     * @param cls 查询对象 class
     * @return 查询对象的所有字段
     */
    private static Field[] getAllFields(Class<?> cls) {
        List<Field> fieldList = new ArrayList<>();
        while (cls != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(cls.getDeclaredFields())));
            cls = cls.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

    /**
     * 解析 Condition，转换为 Search
     *
     * @param condition condition
     * @return Search 查询实例
     */
    private static Search parse(Condition condition) {
        Method[] declaredMethods = Condition.class.getDeclaredMethods();
        JSONObject jsonObject = new JSONObject();
        Arrays.stream(declaredMethods).forEach(method -> {
            try {
                jsonObject.put(method.getName(), method.invoke(condition));
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.error("error={}", e.getCause().toString());
            }
        });
        return jsonObject.toJavaObject(Search.class);
    }

    /**
     * 往 QueryWrapper 实例填充值
     *
     * @param queryWrapper 实例
     * @param search       查询对象
     * @param value        值
     * @param <T>          泛型
     * @throws InvocationTargetException 方法执行异常
     * @throws IllegalAccessException    非法异常
     */
    private static <T> void set(QueryWrapper<T> queryWrapper, Search search, Object value) throws InvocationTargetException, IllegalAccessException {
        Class<?>[] parameterTypes = null;
        Object[] objects = null;
        String keyword = search.getKeyword(), column = search.getColumn();
        switch (keyword.toLowerCase()) {
            case "between":
                String[] dateArr = String.valueOf(value).split(",");
                for (String dateStr : dateArr) {
                    if (!DateUtils.isDate(dateStr, search.getFormat())) {
                        log.warn("{}: {} 不是日期格式，skipping", column, dateStr);
                        return;
                    }
                }
                if (dateArr.length == 1) {
                    keyword = "gt";
                    parameterTypes = new Class[]{boolean.class, Object.class, Object.class};
                    objects = new Object[]{true, column, dateArr[0]};
                    log.debug("{},{} -> {},{} <==", column, search.getKeyword(), keyword, value);
                    break;
                }
                parameterTypes = new Class[]{boolean.class, Object.class, Object.class, Object.class};
                objects = new Object[]{true, column, dateArr[0], dateArr[1]};
                log.debug("{},{},{},{}", column, keyword, dateArr[0], dateArr[1]);
                break;
            case "":
                //todo 扩展其他特殊条件
                break;
            default:
                parameterTypes = new Class[]{boolean.class, Object.class, Object.class};
                objects = new Object[]{true, column, value};
                log.debug("{},{},{}", column, keyword, value);
        }
        invoke(queryWrapper, keyword, parameterTypes, objects);
    }

    /**
     * 执行方法，构造条件
     *
     * @param queryWrapper   查询对象
     * @param keyword        执行方法
     * @param parameterTypes 参数类型
     * @param objects        参数值
     * @param <T>            泛型
     */
    private static <T> void invoke(QueryWrapper<T> queryWrapper, String keyword, Class<?>[] parameterTypes, Object... objects) {
        try {
            queryWrapper.getClass().getSuperclass().getDeclaredMethod(keyword, parameterTypes).invoke(queryWrapper, objects);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            log.error("error={}", e.getCause().toString());
        }
    }

    /**
     * 请求中包含高级查询参数
     *
     * @param request 请求对象
     * @return true 包含 / false 不包含
     */
    public static boolean containsSearch(HttpServletRequest request) {
        Map<String, String[]> parameterMap;
        return !CollectionUtils.isEmpty(parameterMap = request.getParameterMap()) && parameterMap.containsKey(SEARCH_PARAM_NAME);
    }

    /**
     * 请求中包含排序参数
     *
     * @param request 请求对象
     * @return true 包含 / false 不包含
     */
    public static boolean containsSorter(HttpServletRequest request) {
        Map<String, String[]> parameterMap;
        return !CollectionUtils.isEmpty(parameterMap = request.getParameterMap()) && parameterMap.containsKey(SORTER);
    }

}
